﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Xml.Serialization;
using BOL.Models;
using BOL.Models.Genome;
using QuickGraph;
using BOL.Apps.GeneNetworkBrowser.Databases;

namespace BOL.Apps.GeneNetworkBrowser
{
    /// <summary>
    /// Represent a gene network.
    /// </summary>
    [Serializable]
    public class Network : BidirectionalGraph<Gene, Relation>, INamed, IDescribable, ICloneable
    {
        #region Constructors

        /// <summary>
        /// Overloaded. Initializes a new instance of the Network of T class.
        /// </summary>
        public Network(string name, string description, bool allowParallelEdges, int vertexCapacity)
            : base(allowParallelEdges, vertexCapacity)
        {
            _name = name;
            _description = description;
        }

        public Network(string name, string description, bool allowParallelEdges)
            : base(allowParallelEdges)
        {
            _name = name;
            _description = description;
        }

        public Network(string name, string description)
        {
            _name = name;
            _description = description;
        }

        public Network(string name) : this(name, String.Empty) { }

        public Network() : this(String.Empty, String.Empty) { }

        #endregion

        #region Public methods

        public static Network Search(IEnumerable<int> input, uint pattern = 32748)
        {
            var hgr = new GeneRelations_Human();
            var rs = hgr.Relations.Where(r => input.Contains(r.SourceID) && (r.Pattern & pattern) == r.Pattern).ToArray();
            var rt = hgr.Relations.Where(r => input.Contains(r.TargetID) && (r.Pattern & pattern) == r.Pattern).ToArray();

            var gs = rs.Select(r => r.TargetID);
            var gt = rt.Select(r => r.SourceID);

            var gc = gs.Union(gt).ToArray();
            var rcs = hgr.Relations.Where(r => gc.Contains(r.SourceID) && (r.Pattern & pattern) == r.Pattern).ToArray();
            var rct = hgr.Relations.Where(r => gc.Contains(r.TargetID) && (r.Pattern & pattern) == r.Pattern).ToArray();

            var relations = rs.Concat(rt).Concat(rcs.Intersect(rct)).ToList();
            var genes = input.Union(gc);

            if (relations.Any())
            {
                var network = new Network();
                var dict = genes.ToDictionary(gene => gene, gene => new Gene(gene));
                var edges = relations.Select(relation => new Relation(dict[relation.SourceID], dict[relation.TargetID], relation.Pattern)).ToList();

                network.AddVerticesAndEdgeRange(edges);

                return network;
            }
            
            return null;
        }

        //public static Network Search(IEnumerable<int> input, uint pattern)
        //{
        //    var hgr = new GeneRelations_Human();
        //    var relations = hgr.Relations.Where(relation => input.Contains(relation.SourceID) || input.Contains(relation.TargetID));

        //    if (relations.Count() > 0)
        //    {
        //        var genes = new Dictionary<int, Gene>();

        //        foreach (var relation in relations)
        //        {
        //            int sourceID = relation.SourceID;
        //            int targetID = relation.TargetID;

        //            if (!genes.ContainsKey(sourceID))
        //                genes.Add(sourceID, new Gene(sourceID));
        //            if (!genes.ContainsKey(targetID))
        //                genes.Add(targetID, new Gene(targetID));
        //        }

        //        foreach (var geneId in input)
        //            if (!genes.ContainsKey(geneId))
        //                genes.Add(geneId, new Gene(geneId));

        //        relations = hgr.Relations.Where(relation => genes.Keys.Contains(relation.SourceID) && genes.Keys.Contains(relation.TargetID) && (relation.Pattern & pattern) == relation.Pattern);

        //        var edges = new List<Relation>();
        //        foreach (var relation in relations)
        //            edges.Add(new Relation(genes[relation.SourceID], genes[relation.TargetID], relation.Pattern));

        //        var network = new Network();
        //        network.AddVerticesAndEdgeRange(edges);

        //        return network;
        //    }
        //    else
        //        return null;
        //}

        public void Annotate()
        {
            var annotations = new Annotations_Human();

            foreach (var gene in Vertices)
            {
                try
                {
                    var gi = annotations.GeneInfo.Single(geneinfo => geneinfo.ID.Equals(gene.Id));
                    gene.Symbol = gi.Symbol;
                    if (!String.IsNullOrEmpty(gi.Alternatives))
                        gene.Alternatives = gi.Alternatives.Split('|');
                    if (!String.IsNullOrEmpty(gi.Chromosome))
                        gene.Chromosome = GetChromosome(gi.Chromosome);
                    if (!String.IsNullOrEmpty(gi.Cytoband))
                        gene.CytoBand = CytoBand.Parse(gi.Cytoband);
                    if (gi.GeneType != 0)
                        gene.Type = GetGeneType(gi.GeneType);
                    if (!String.IsNullOrEmpty(gi.FullName))
                        gene.FullName = gi.FullName;
                }
                catch (InvalidOperationException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }

        private Chromosome GetChromosome(string chrStr)
        {
            switch (chrStr)
            {
                default:
                    return new Chromosome(9606, "Chr1");
                case "2":
                    return new Chromosome(9606, "Chr2");
                case "3":
                    return new Chromosome(9606, "Chr3");
                case "4":
                    return new Chromosome(9606, "Chr4");
                case "5":
                    return new Chromosome(9606, "Chr5");
                case "6":
                    return new Chromosome(9606, "Chr6");
                case "7":
                    return new Chromosome(9606, "Chr7");
                case "8":
                    return new Chromosome(9606, "Chr8");
                case "9":
                    return new Chromosome(9606, "Chr9");
                case "10":
                    return new Chromosome(9606, "Chr10");
                case "11":
                    return new Chromosome(9606, "Chr11");
                case "12":
                    return new Chromosome(9606, "Chr12");
                case "13":
                    return new Chromosome(9606, "Chr13");
                case "14":
                    return new Chromosome(9606, "Chr14");
                case "15":
                    return new Chromosome(9606, "Chr15");
                case "16":
                    return new Chromosome(9606, "Chr16");
                case "17":
                    return new Chromosome(9606, "Chr17");
                case "18":
                    return new Chromosome(9606, "Chr18");
                case "19":
                    return new Chromosome(9606, "Chr19");
                case "20":
                    return new Chromosome(9606, "Chr20");
                case "21":
                    return new Chromosome(9606, "Chr21");
                case "22":
                    return new Chromosome(9606, "Chr22");
                case "X":
                    return new Chromosome(9606, "ChrX");
                case "Y":
                    return new Chromosome(9606, "ChrY");
            }
        }

        private Models.Features.GeneType GetGeneType(short type)
        {
            switch (type)
            {
                case 1:
                    return Models.Features.GeneType.ProteinCoding;
                case 2:
                    return Models.Features.GeneType.Pseudo;
                case 3:
                    return Models.Features.GeneType.RibosomalRna;
                case 4:
                    return Models.Features.GeneType.TransferRna;
                case 5:
                    return Models.Features.GeneType.SmallCytoplasmicRna;
                case 6:
                    return Models.Features.GeneType.SmallNucleolarRna;
                case 7:
                    return Models.Features.GeneType.SmallNuclearRna;
                case 8:
                    return Models.Features.GeneType.NonCodingRna;
                case 9:
                    return Models.Features.GeneType.MiscellaneousRna;
                case 10:
                    return Models.Features.GeneType.Other;
                default:
                    return Models.Features.GeneType.Unknown;
            }
        }

        public void ApplyGeneValues(string propertyName, Dictionary<int, double> attribute)
        {
            foreach (var gene in Vertices)
                if (attribute.ContainsKey(gene.Id))
                    gene.Attributes.Add(propertyName, attribute[gene.Id]);
        }

        #endregion

        #region INamed implementation

        private string _name;

        [Category("Information")]
        [Description("Returns the name of the network.")]
        [XmlAttribute]
        public string Name { get { return _name; } set { _name = value; } }

        #endregion

        #region IDescribable implementation

        private string _description;

        /// <summary>Gets or sets description of Network.</summary>
        [Category("Information")]
        [Description("Returns the description of the network.")]
        [XmlAttribute]
        public string Description { get { return _description; } set { _description = value; } }

        #endregion        
        
        #region ICloneable implementation

        /// <summary>
        /// Creates an exact copy of current Network.
        /// </summary>
        /// <returns></returns>
        public new Network Clone()
        {
            Network clone = new Network(_name, _description);
            clone.AddVerticesAndEdgeRange(Edges);
            
            return clone;
        }

        /// <summary>
        /// Creates an exact copy of current T as an object. 
        /// </summary>
        /// <returns></returns>
        object ICloneable.Clone()
        {
            return Clone();
        }

        #endregion

        #region IEquatable<Network> implementation

        public bool Equals(Network network)
        {
            return GetHashCode() == network.GetHashCode();
        }

        #endregion

        #region ISelectable implementation

        [Browsable(false)]
        public bool IsSelected
        {
            get
            {
                foreach (var gene in Vertices)
                    if (gene.IsSelected)
                        return true;
                foreach (var relation in Edges)
                    if (relation.IsSelected)
                        return true;

                return false;
            }
        }

        public void Select()
        {
            foreach (var gene in Vertices)
                gene.Select();
            foreach (var relation in Edges)
                relation.Select();
        }

        public void Deselect()
        {
            foreach (var gene in Vertices)
                gene.Deselect();
            foreach (var relation in Edges)
                relation.Deselect();
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return Edges.Sum(edge => edge.GetHashCode());
        }

        public override bool Equals(object obj)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");

            if (!(obj is Network))
                throw new InvalidCastException("The 'obj' argument is not a Gene object.");

            return Equals(obj as Network);
        }

        public override string ToString()
        {
            return String.Format("{0} (#N:{1}, #E:{2})", String.IsNullOrEmpty(Name) ? "Network" : Name, VertexCount, EdgeCount);
        }

        #endregion
    }
}
